package triannon.client.components;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Iterator;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;

import triannon.client.map.IMap;
import triannon.client.map.MapFactory;
import triannon.client.selection.Selection;
import triannon.client.selection.SelectionEvent;
import triannon.client.selection.Selection.SelectedObject;
import triannon.client.settings.LevelProperties;
import triannon.client.settings.RenderSettings;
import triannon.client.sprites.Building;
import triannon.client.sprites.Field;
import triannon.client.sprites.ISelection;
import triannon.client.sprites.ISprite;
import triannon.client.sprites.LivingSprite;
import triannon.client.sprites.SpriteList;
import triannon.client.sprites.Unit;
import triannon.client.sprites.renderer.Renderers;
import triannon.client.util.DebugUtil;
import triannon.client.util.TMath;
import triannon.client.util.timer.TTimer;
import triannon.client.util.timer.TTimerListener;

@SuppressWarnings("serial")
public class Canvas extends JPanel implements IDrag, TTimerListener {
	private final JScrollPane scrollPane;
	private final Field field;

	private final IMap level;
	private final PointAndKlickListener pointAndKlickListener;

	public Canvas(JScrollPane scrollPane) {
		this.scrollPane = scrollPane;

		level = MapFactory.createRandomLevelNewStyle();
		field = level.getField();

		setPreferredSize(new Dimension(level.getWidth(), level.getHeight()));
		setSize(getPreferredSize());

		pointAndKlickListener = new PointAndKlickListener();

		addMouseListener(pointAndKlickListener);
		addMouseMotionListener(pointAndKlickListener);

		TTimer.getInstance().addSTimerListener(this);
	}

	@Override
	public void drag(int dx, int dy) {
		JViewport vp = scrollPane.getViewport();

		int nx = vp.getViewPosition().x + dx;
		int ny = vp.getViewPosition().y + dy;

		if (nx < 0) {
			nx = 0;
		}
		if (ny < 0) {
			ny = 0;
		}
		int maxx = getWidth() - vp.getWidth();
		if (nx > maxx) {
			nx = maxx;
		}
		int maxy = getHeight() - vp.getHeight();
		if (ny > maxy) {
			ny = maxy;
		}
		vp.setViewPosition(new Point(nx, ny));
	}

	@Override
	public Field getTiles() {
		return field;
	}

	@Override
	public double getScale() {
		return 1.0;
	}

	@Override
	public void setViewPort(int dx, int dy) {
		JViewport vp = scrollPane.getViewport();
		int width = vp.getWidth();
		int height = vp.getHeight();

		int newX = dx - width / 2;
		int newY = dy - height / 2;

		/*
		 * forces the ViewPort into the map
		 */
		if (newX < 0) {
			newX = 0;
		}
		int maxX = getWidth() - vp.getWidth();
		if (newX > maxX) {
			newX = maxX;
		}

		if (newY < 0) {
			newY = 0;
		}

		int maxY = getHeight() - vp.getHeight();
		if (newY > maxY) {
			newY = maxY;
		}
		vp.setViewPosition(new Point(newX, newY));
	}

	@Override
	public void update(long tpf) {
		for (ISprite sprite : field) {
			sprite.update(tpf);
		}
		repaint();
	}

	@Override
	public void paint(Graphics graphics) {
		Graphics2D g = (Graphics2D) graphics;

		g.setRenderingHints(RenderSettings.getInstance().getQualityHints());

		JViewport vp = scrollPane.getViewport();
		Rectangle visibleRect = vp.getViewRect();

		g.clearRect(visibleRect.x, visibleRect.y, visibleRect.width,
				visibleRect.height);

		// reset the alreadyPainted-flag
		Iterator<ISprite> it = level.getField().iteratorForRect(visibleRect);
		while (it.hasNext()) {
			ISprite sprite = it.next();
			sprite.setAlreadyPainted(false);
		}

		// paint all sprites
		it = level.getField().iteratorForRect(visibleRect);
		while (it.hasNext()) {
			ISprite sprite = it.next();
			sprite.paint(g);
		}

		if (DebugUtil.getInstance().isRenderBlockedSectors()) {
			Renderers.getBlockedsectorrenderer().paint(g, field, visibleRect);
		}

		// paint the grid, if needed
		if (DebugUtil.getInstance().isRenderGrid()) {
			g.setColor(DebugUtil.getInstance().getGridColor());
			for (int x = 0; x < getWidth(); x += LevelProperties.getGridSize()) {
				g.drawLine(x, 0, x, getHeight());
			}

			for (int y = 0; y < getHeight(); y += LevelProperties.getGridSize()) {
				g.drawLine(0, y, getWidth(), y);
			}
		}

		// paint the selection frame
		pointAndKlickListener.paint(g);

		Toolkit.getDefaultToolkit().sync();
	}

	private class PointAndKlickListener extends MouseAdapter {
		private Point lastMouseClick;
		private boolean dragged = false;
		private final Rectangle selectionRect = new Rectangle();
		private int lastMouseButton = 0;

		private final Color SELECTION_RECT_COLOR = new Color(192, 192, 192, 192);

		private SpriteList selectedSprites = new SpriteList();

		@Override
		public void mousePressed(MouseEvent e) {
			lastMouseButton = e.getButton();
			if (lastMouseButton == MouseEvent.BUTTON1) {
				lastMouseClick = e.getPoint();
			}
		}

		@Override
		public void mouseDragged(MouseEvent e) {
			if (lastMouseButton == MouseEvent.BUTTON1) {
				dragged = true;
				Point dragToPoint = e.getPoint();
				int minX = Math.min(lastMouseClick.x, dragToPoint.x);
				int minY = Math.min(lastMouseClick.y, dragToPoint.y);
				int maxX = Math.max(lastMouseClick.x, dragToPoint.x);
				int maxY = Math.max(lastMouseClick.y, dragToPoint.y);
				selectionRect.setBounds(minX, minY, maxX - minX, maxY - minY);

				for (ISprite sprite : field) {
					if (sprite instanceof ISelection) {
						((ISelection) sprite).setPreSelected(selectionRect.contains(sprite
								.getRect()));
					}
				}

				repaint();
			}
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			if (e.getButton() == MouseEvent.BUTTON1) {
				if (dragged) {
					dragged = false;

					selectedSprites = new SpriteList();
					SelectedObject object = null;

					for (ISprite sprite : field) {
						if (sprite instanceof ISelection) {
							((ISelection) sprite).setPreSelected(false);
							if (selectionRect.contains(sprite.getRect())) {
								// find the type of the selected object
								if (sprite instanceof Unit) {
									object = SelectedObject.Unit;
								} else if (sprite instanceof Building
										&& object != SelectedObject.Unit) {
									object = SelectedObject.Building;
								}
							}
						}
					}

					if (object != null) {
						for (ISprite sprite : field) {
							if (sprite instanceof ISelection) {
								boolean select = selectionRect.contains(sprite.getRect())
										&& ((object == SelectedObject.Building && sprite instanceof Building) || (object == SelectedObject.Unit && sprite instanceof Unit));
								((ISelection) sprite).setSelected(select);
								if (select) {
									selectedSprites.add(sprite);
								}
							}
						}

						Selection.fireSelectionChanged(new SelectionEvent(object,
								selectedSprites));

						repaint();
					}
				}

				boolean selectNew = false;
				// if shift is clicked
				boolean invertCurrent = (e.getModifiers() & InputEvent.SHIFT_MASK) != 0;

				SelectedObject object = null;

				for (ISprite sprite : field.getSpritesAtPoint(e.getPoint())) {
					if (sprite instanceof ISelection) {
						selectNew = true;

						if (invertCurrent) {
							// when shift is down, simply invert the selection of the clicked
							// sprite and return
							boolean isSelected = !((ISelection) sprite).isSelected();
							((ISelection) sprite).setSelected(isSelected);
							if (isSelected) {
								selectedSprites.add(sprite);

								if (sprite instanceof Building) {
									object = SelectedObject.Building;
								} else if (sprite instanceof Unit) {
									object = SelectedObject.Unit;
								} else {
									object = SelectedObject.Enemy;
								}
							} else {
								selectedSprites.remove(sprite);
							}

							Selection.fireSelectionChanged(new SelectionEvent(object,
									selectedSprites));

							repaint();
							return;
						}
					}
				}

				if (selectNew) {
					selectedSprites = new SpriteList();

					for (ISprite sprite : field) {
						if (sprite instanceof ISelection) {
							// if this sprite is clicked
							boolean isClicked = sprite.getRect().contains(e.getPoint());

							// set the selection
							((ISelection) sprite).setSelected(isClicked);

							if (isClicked) {
								System.err.println(sprite);
								selectedSprites.add(sprite);
								if (sprite instanceof Building) {
									object = SelectedObject.Building;
								} else if (sprite instanceof Unit) {
									object = SelectedObject.Unit;
								} else {
									object = SelectedObject.Enemy;
								}

								Selection.fireSelectionChanged(new SelectionEvent(object,
										selectedSprites));

								repaint();
							}
						}
					}
				}
			} else if (e.getButton() == MouseEvent.BUTTON3) {
				if (!dragged) {
					for (ISprite sprite : Selection.getSelectedSprite()) {
						LivingSprite lSprite = (LivingSprite) sprite;

						Point moveTo = e.getPoint();

						moveTo.x = TMath.toGridCenter(moveTo.x);
						moveTo.y = TMath.toGridCenter(moveTo.y);

						for (ISprite sprite2 : field.getSpritesAtPoint(e.getPoint())) {
							if (sprite2 instanceof LivingSprite) {
								moveTo.x = ((LivingSprite) sprite2).getCenterX();
								moveTo.y = ((LivingSprite) sprite2).getCenterY();
								break;
							}
						}

						field.findPath(lSprite, TMath.toLevelCoorninates(moveTo.x), TMath
								.toLevelCoorninates(moveTo.y));

						lSprite.setGoTo(moveTo);
					}
					repaint();
				}
			}
		}

		@Override
		public void mouseClicked(MouseEvent e) {

		}

		@Override
		public void mouseMoved(MouseEvent e) {
			for (ISprite sprite : field) {
				if (sprite instanceof LivingSprite) {
					boolean mouseOver = sprite.getRect().contains(e.getPoint());

					((LivingSprite) sprite).setHealthBarVisible(mouseOver);
					((LivingSprite) sprite).setPreSelected(mouseOver);
				}
			}
			repaint();
		}

		public void paint(Graphics2D g) {
			if (dragged) {
				g.setColor(SELECTION_RECT_COLOR);

				g.draw(selectionRect);
			}
		}
	}

}
